using System;
using System.IO;
using System.Collections.Generic;
using System.Threading;
using System.ComponentModel;

using Gtk;
using MonsterNet;

namespace MonsterNetWindows
{
	public class MNMenuItem : Gtk.MenuItem {
		private TreeView _tv;
		
		public MNMenuItem (string label, TreeView sender) : base(label)
		{
			_tv = sender;
		}
	

		public TreeView Tv {
			get {
				return this._tv;
			}
		}
	}
	
	public partial class MNMainWindow : Gtk.Window
	{
		private const int kNoteBookPageFiles = 0;
		private const int kNoteBookPageQueue = 1;
		
		private const int kFilterAt = 1;
		
		private MNController _controller;
		private MNLocation _location;
		
		private MNController.ScanningEvent _scanningHandler;
		private MNController.CopyEvent _copyingHandler;
		private MNController.SaveLoadEvent _saveLoadHandler;
	
		private DateTime _activityStartTime;
		
		private bool _lastScanOnly = false;
		
		private Gtk.TreeModelFilter _filterDirectories;
		private Gtk.TreeModelSort _sortDirectories;
		private Gtk.TreeModelFilter _filterFiles;
		private Gtk.TreeModelSort _sortFiles;
		
		private struct HandleMultiSelectedFSObjectsInfo {
			public MNFSObjectLocation Location;
			public MNSynchronisedFSObject SyncObj;
			public bool UpdateValue;
		}

		private delegate void HandleMultiSelectedFSObjects(HandleMultiSelectedFSObjectsInfo info);
		
		private bool _blockEvents = false;
		
		private MNBusyWindow _busyWindow;
		
		#region Constructor/setup treeviews
		
		public MNMainWindow (MNBusyWindow busyWindow) : base(Gtk.WindowType.Toplevel)
		{
			this.Build ();
			this.Visible = false;
			//StartLoad ();
			_busyWindow = busyWindow;
			
			progressbarProcessing.Text = @"";
			
			stopAction.Sensitive = false;
			stopAction.Tooltip = @"Stop";
			
			SetupTreeViewDirectories ();
			SetupTreeViewFiles ();
			SetupTreeViewQueue ();
			
			FilesAction.Active = true;
			
		}
		
		public void LoadController ()
		{
			this.Visible = false;
			_location = MNSerialise.LoadFromFile<MNLocation> (MNGlobal.DefaultDirectoryWithFilename (MNLocation.Savefilename));
			_saveLoadHandler = new MNController.SaveLoadEvent (OnSaveLoad);
			_controller = new MNController (_location, _saveLoadHandler);
			//invoke on main thread...
			Gtk.Application.Invoke (delegate { LoadComplete (); });
		}
		
		private void StartLoad ()
		{
			this.Visible = false;
			_busyWindow = new MNBusyWindow ();
			WindowPosition busyPos = new Gtk.WindowPosition ();
			
			_busyWindow.SetPosition (busyPos);
			_busyWindow.Show ();
			_busyWindow.Message = "Loading";
			_busyWindow.Pulse ();
			_busyWindow.QueueDraw ();
		}
		
		private void LoadComplete ()
		{
			_scanningHandler = new MNController.ScanningEvent (OnScanning);
			_controller.SubscribeScanning (_scanningHandler);
			
			_copyingHandler = new MNController.CopyEvent (OnCopying);
			_controller.SubscribeCopying (_copyingHandler);
			
			_busyWindow.Stop = true;
			_busyWindow.Visible = false;
			this.Visible = true;

			if(_location.CurrentLocation == MNFSObjectLocation.NumberOfLocations) {
				MessageDialog md = new MessageDialog(this,
				                                     DialogFlags.Modal,
				                                     MessageType.Info,
				                                     ButtonsType.Ok,
				                                     string.Format(@"Welcome to MonsterNET before you get started you have to setup your current location and its paths. 
NOTE: In Windows these path can't be a network UNC path, you must map a UNC path to a drive letter."));
				md.Run();
				md.Destroy();
				
				ShowPreferenceDialog();
			} else {
				ObjectToControls();
			}			
		}		
		
		private void SetupTreeViewDirectories() {
			//initalise treeview columns
						
			Gtk.TreeViewColumn folderColumn =  SetupTreeViewColumn(@"Folders", 200);			
			CellRendererPixbuf folderIconRenderer = new Gtk.CellRendererPixbuf();
			folderColumn.PackStart(folderIconRenderer, false);			
			CellRendererText folderNameRenderer = new Gtk.CellRendererText();
			folderColumn.PackStart(folderNameRenderer, true);
			
			Gtk.TreeViewColumn sizeColumn = SetupTreeViewColumn(@"Size", 70);	
			Gtk.CellRendererText sizeCell = new Gtk.CellRendererText();
			
			sizeCell.Alignment = Pango.Alignment.Right;
			sizeColumn.PackEnd(sizeCell, true);
				
			folderColumn.SetCellDataFunc (folderIconRenderer, new Gtk.TreeCellDataFunc (RenderDirectoryFolderIconCell));
			folderColumn.SetCellDataFunc (folderNameRenderer, new Gtk.TreeCellDataFunc (RenderDirectoryFolderNameCell));
			sizeColumn.SetCellDataFunc (sizeCell, new Gtk.TreeCellDataFunc (RenderDirectorySizeCell));
		
			treeviewFolders.AppendColumn(folderColumn);
			treeviewFolders.AppendColumn(sizeColumn);
			
			treeviewFolders.Selection.Mode = SelectionMode.Multiple;
		}
		
		private void SetupTreeViewFiles() {
			//initalise treeview columns
						
			Gtk.TreeViewColumn filenameColumn =  SetupTreeViewColumn(@"Filename", 200);			
			CellRendererPixbuf fileIconRenderer = new Gtk.CellRendererPixbuf();
			filenameColumn.PackStart(fileIconRenderer, false);			
			CellRendererText fileNameRenderer = new Gtk.CellRendererText();
			filenameColumn.PackStart(fileNameRenderer, true);			
			
			Gtk.TreeViewColumn sizeColumn = SetupTreeViewColumn(@"Size", 70);
			Gtk.CellRendererText sizeCell = new Gtk.CellRendererText();
			sizeColumn.PackStart(sizeCell,false);
			
			Gtk.TreeViewColumn dateModifiedColumn = SetupTreeViewColumn(@"Date Modified", 100);
			Gtk.CellRendererText dateModifiedCell = new Gtk.CellRendererText();
			dateModifiedColumn.PackStart(dateModifiedCell,false);
			
			filenameColumn.SetCellDataFunc (fileNameRenderer, new Gtk.TreeCellDataFunc (RenderFilenameCell));
			filenameColumn.SetCellDataFunc (fileIconRenderer, new Gtk.TreeCellDataFunc (RenderFilenameIconCell));
			sizeColumn.SetCellDataFunc (sizeCell, new Gtk.TreeCellDataFunc (RenderFileSizeCell));
			dateModifiedColumn.SetCellDataFunc (dateModifiedCell, new Gtk.TreeCellDataFunc(RenderFileDateModifiedCell));
			
			treeviewFiles.AppendColumn(filenameColumn);
			treeviewFiles.AppendColumn(sizeColumn);
			treeviewFiles.AppendColumn(dateModifiedColumn);
			
			treeviewFiles.Selection.Mode = SelectionMode.Multiple;
		}		
		
		private void SetupTreeViewQueue() {
			//initialise the queues columns
			
			//name
			Gtk.TreeViewColumn nameColumn = SetupTreeViewColumn(@"File", 200);
			Gtk.CellRendererText nameCell = new Gtk.CellRendererText();
			nameColumn.PackStart(nameCell, true);
			
			//path
			Gtk.TreeViewColumn pathColumn = SetupTreeViewColumn(@"Path", 300);
			Gtk.CellRendererText pathCell = new Gtk.CellRendererText();
			pathColumn.PackStart(pathCell, true);
			
			//from
			Gtk.TreeViewColumn fromColumn = SetupTreeViewColumn(@"From", 100);
			Gtk.CellRendererText fromCell = new Gtk.CellRendererText();
			fromColumn.PackStart(fromCell, true);
			
			//to
			Gtk.TreeViewColumn toColumn = SetupTreeViewColumn(@"To", 100);
			Gtk.CellRendererText toCell = new Gtk.CellRendererText();
			toColumn.PackStart(toCell, true);
			
			//size
			Gtk.TreeViewColumn sizeColumn = SetupTreeViewColumn(@"Size", 70);
			Gtk.CellRendererText sizeCell = new Gtk.CellRendererText();
			sizeColumn.PackStart(sizeCell,false);			
			
			nameColumn.SetCellDataFunc (nameCell, new Gtk.TreeCellDataFunc (RenderQueueNameCell));
			pathColumn.SetCellDataFunc (pathCell, new Gtk.TreeCellDataFunc (RenderQueuePathCell));
			fromColumn.SetCellDataFunc (fromCell, new Gtk.TreeCellDataFunc (RenderQueueFromCell));
			toColumn.SetCellDataFunc (toCell, new Gtk.TreeCellDataFunc (RenderQueueToCell));
			sizeColumn.SetCellDataFunc (sizeCell, new Gtk.TreeCellDataFunc (RenderQueueSizeCell));
			
			treeviewQueue.AppendColumn(nameColumn);
			treeviewQueue.AppendColumn(pathColumn);
			treeviewQueue.AppendColumn(fromColumn);
			treeviewQueue.AppendColumn(toColumn);
			treeviewQueue.AppendColumn(sizeColumn);
			
			treeviewQueue.Selection.Mode = SelectionMode.None;

		}
		
		private TreeViewColumn SetupTreeViewColumn(string title, int width) {
			
			TreeViewColumn tvc = new TreeViewColumn();
			tvc.Title = title;
			tvc.Sizing = TreeViewColumnSizing.Fixed;
			tvc.Resizable = true;
			tvc.FixedWidth = width;			
			tvc.MinWidth = 30;
			
			return tvc;
		}
			
		#endregion
		
		#region events (window)
		
		private void SaveAndExit ()
		{
			Console.WriteLine (@"Saving and Exiting");
			
			_controller.UnsubscribeScanning (_scanningHandler);
			_controller.UnsubscribeCopying (_copyingHandler);
						
			this.Visible = false;
			_busyWindow = new MNBusyWindow ();
			_busyWindow.Message = @"Saving";
			_busyWindow.Show ();
			
			// save in the background
			BackgroundWorker saver = new BackgroundWorker();
			saver.DoWork += new DoWorkEventHandler(SaveAndExitThread);
			saver.RunWorkerAsync();	
		}
		
		//TODO: make the controller send saving events.
		private void SaveAndExitThread (object sender, DoWorkEventArgs e)
		{
			_controller.Save ();
			Gtk.Application.Invoke (delegate {
				_controller.UnsubscribeSaveLoad (_saveLoadHandler);
				_busyWindow.Destroy ();
				Application.Quit ();
			});
			
		}
		
		protected void OnDeleteEvent(object sender, DeleteEventArgs a) {
			SaveAndExit();
			a.RetVal = true;	
		}		
		
		protected virtual void OnQuitActionActivated (object sender, System.EventArgs e)
		{
			SaveAndExit ();
		}
		
		
		protected virtual void OnScanActionActivated (object sender, System.EventArgs e)
		{
			if (_location != null)
			{
				_controller.CancelAction = false;
				if (_controller.Scan ())
				{
					// scanning started.
					_activityStartTime = DateTime.Now;
					stopAction.Sensitive = true;
					stopAction.Tooltip = @"Stop Scanning";
					scanAction.Sensitive = false;
					QueueAction.Sensitive = false;
					preferencesAction.Sensitive = false;
					treeviewFolders.Sensitive = false;
					treeviewFiles.Sensitive = false;
					// start pulsing the progress bar
					GLib.Timeout.Add (100, new GLib.TimeoutHandler (StatusPulse));
				}
			}	
		}
		
		protected virtual void ShowPreferenceDialog ()
		{
			MNPreferenceDialog prefWin = new MNPreferenceDialog (_location, _controller.ExtensionsToIgnore);
			
			if (prefWin.Run () == (int)Gtk.ResponseType.Ok)
			{
				if (_controller.LocationUpdated(prefWin.RestoredBackup) || prefWin.HasChanged )
				{
					MNSerialise.SaveToFile<MNLocation>(MNGlobal.DefaultDirectoryWithFilename(MNLocation.Savefilename), _location);				
					_controller.HasChanged = prefWin.HasChanged; 
					ObjectToControls();
				}
			}			
			
			prefWin.Destroy();
		}
		
		protected virtual void OnPreferencesActionActivated (object sender, System.EventArgs e)	{
			ShowPreferenceDialog();			
		}
						
		protected virtual void OnStopActionActivated (object sender, System.EventArgs e)
		{
			//set the controller to cancel scanning.
			_controller.CancelAction = true;
		}
	
		protected virtual void OnDisplayButtonToggled(object sender, System.EventArgs e) {
			if((sender as RadioAction).Active) {
				ShowFilesActions(FilesAction.Active);
				ShowQueueActions(QueueAction.Active);
				//ShowMonsterNetActions(MonsterNetAction.Active);
			}
		}
				
		private void ShowFilesActions(bool visible) {
			scanAction.Visible = visible;
			NewOnlyAction.Visible = visible;
			if(visible) {
				notebookMain.CurrentPage = kNoteBookPageFiles;
			}
		}
		
		private void ShowQueueActions(bool visiable) {
			copyAction.Visible = visiable;
			if(visiable && _controller.Enqueue()) {
				// display the queue
				QueueToControls();
				notebookMain.CurrentPage = kNoteBookPageQueue;
			}
		}
		
		protected virtual void OnCopyActionActivated (object sender, System.EventArgs e) {
			if (_location != null) {
				if(_controller.Queue.Count > 0) {
					_controller.CancelAction = false;
					if(_controller.CopyQueue()) {
						// copying started.
						_activityStartTime = DateTime.Now;
						stopAction.Sensitive = true;
						stopAction.Tooltip = @"Stop Copying";
						scanAction.Sensitive = false;
						QueueAction.Sensitive = false;
						copyAction.Sensitive = false;
						FilesAction.Sensitive = false;
						preferencesAction.Sensitive = false;
						progressbarProcessing.Fraction = 0;
					} else {
						//TODO: copying didn't start, event should have been raised.
						//progressbarProcessing.Text = @"Copy Failed";		
					}
				}
			}			
		}
		
		protected virtual void OnNewOnlyActionActivated (object sender, System.EventArgs e) {
			_lastScanOnly = NewOnlyAction.Active;
			ObjectToControls();
		}
		
		protected virtual void OnEntryDirectorySearchChanged (object sender, System.EventArgs e)
		{
			_filterDirectories.Refilter();	
			_filterFiles.Refilter();
		}
		

		protected int SortSynchronisedFSObjectTree(TreeModel model, TreeIter t1, TreeIter t2) { 
			MNSynchronisedFSObject syncObject1 = (MNSynchronisedFSObject)model.GetValue(t1, 0);
			MNSynchronisedFSObject syncObject2 = (MNSynchronisedFSObject)model.GetValue(t2, 0);
			return string.Compare(syncObject1.Name, syncObject2.Name, true);;
		}
		
		protected int SortQueueByOrder(TreeModel model, TreeIter t1, TreeIter t2) {
			MNQueuedFile qFile1 = (MNQueuedFile)model.GetValue(t1, 0);
			MNQueuedFile qFile2 = (MNQueuedFile)model.GetValue(t2, 0);
			
			int result = 1;
			if(qFile1.Order > qFile2.Order) result = 2;
			return result;
		}
		
		private bool FilterFilesTree(TreeModel model, TreeIter iter) {
			MNSynchronisedFile syncFile = (MNSynchronisedFile)model.GetValue (iter, 0);
			if(entryDirectorySearch.Text.Length < kFilterAt)
				return true;
			
			if(syncFile.Name.ToUpper().IndexOf(entryDirectorySearch.Text.ToUpper()) > -1) {
				return true;
			} else {
				return false;
			}
		}
		
		private bool FilterDirectoriesTree(TreeModel model, TreeIter iter) {
			MNSynchronisedDirectory syncDir = (MNSynchronisedDirectory)model.GetValue (iter, 0);
			if(entryDirectorySearch.Text.Length < kFilterAt)
				return true;
			
			if(syncDir.Name.ToUpper().IndexOf(entryDirectorySearch.Text.ToUpper()) > -1) {
				return true;
			} else {
				return syncDir.ChildrenSearch(entryDirectorySearch.Text.ToUpper());
			}
			
		}		

		#endregion
		
		#region events (trees)
		
		// update actions and files to the newly selected folder
		protected virtual void OnTreeviewFoldersCursorChanged (object sender, System.EventArgs e) {
			TreeIter iter;

			if(GetCurrentTreeViewIter(treeviewFolders, out iter)) {
				MNSynchronisedDirectory syncDir = (MNSynchronisedDirectory)treeviewFolders.Model.GetValue(iter, 0);
				if(syncDir != null) {
					SyncFilesToTreeView(syncDir);
					_blockEvents = true;
					try {
						SyncDirectoryHereAction.Active = syncDir.GetSynchroniseToForLocation(_location.CurrentLocation);
						SyncDirectoryThereAction.Active = syncDir.GetSynchroniseToForLocation(_location.OppositeLocation);
						PrivateDirectoryAction.Active = syncDir.Private;
						IgnoreDirectoryAction.Active = syncDir.Ignore;
					} finally {
						
						_blockEvents = false;
					}
				}
			}
		}
		
		// update the actions for the selected file
		protected virtual void OnTreeviewFilesCursorChanged (object sender, System.EventArgs e)	{
			TreeIter iter;

			if(GetCurrentTreeViewIter(treeviewFiles, out iter)) {
				MNSynchronisedFile syncFile = (MNSynchronisedFile)treeviewFiles.Model.GetValue(iter, 0);
				if(syncFile != null) {					
					_blockEvents = true;
					try {
						SyncFileHereAction.Active = syncFile.GetSynchroniseToForLocation(_location.CurrentLocation);
						SyncFileThereAction.Active = syncFile.GetSynchroniseToForLocation(_location.OppositeLocation);
						PrivateFileAction.Active = syncFile.Private;
						IgnoreFileAction.Active = syncFile.Ignore;
					} finally {
						_blockEvents = false;
					}
				}
			}			
		}
				
		
		protected virtual void OnPrivateFileActionToggled (object sender, System.EventArgs e) {
			if(!_blockEvents)
				SetPrivate(treeviewFiles, PrivateFileAction.Active);
		}
				
		
		protected virtual void OnFileUpActionActivated (object sender, System.EventArgs e) {
			if(!_blockEvents) 
				SetSyncToForLocation(treeviewFiles, _location.OppositeLocation, SyncFileThereAction.Active);
		}
		
		protected virtual void OnSyncFileHereActionToggled (object sender, System.EventArgs e) {
			if(!_blockEvents) 
				SetSyncToForLocation(treeviewFiles, _location.CurrentLocation, SyncFileHereAction.Active);
		}		
		
		protected virtual void OnPrivateDirectoryActionToggled (object sender, System.EventArgs e) {
			if(!_blockEvents) {
				SetPrivate(treeviewFolders, PrivateDirectoryAction.Active);			
				treeviewFiles.QueueDraw();			
			}
		}
		
		protected virtual void OnSyncDirectoryThereActionToggled (object sender, System.EventArgs e) {
			if(!_blockEvents) 
				SetSyncToForLocation(treeviewFolders, _location.OppositeLocation, SyncDirectoryThereAction.Active);			
		}
		
		protected virtual void OnSyncDirectoryHereActionToggled (object sender, System.EventArgs e) {
			if(!_blockEvents) 
				SetSyncToForLocation(treeviewFolders, _location.CurrentLocation, SyncFileHereAction.Active);
		}
			
		protected virtual void OnIgnoreActionActivated (object sender, System.EventArgs e) {
		}
		
		protected virtual void OnIgnoreDirectoryActionToggled (object sender, System.EventArgs e) {
			if(!_blockEvents) 
				SetIgnore(treeviewFolders, IgnoreDirectoryAction.Active);			
		}		
		
		protected virtual void OnIgnoreFileActionActivated (object sender, System.EventArgs e)
		{
			if (!_blockEvents)
				SetIgnore (treeviewFiles, IgnoreFileAction.Active);
		}
		
		protected virtual void OnSearchIMDB (object sender, System.EventArgs e)
		{
			string baseURL = "http://www.imdb.com/find?s=all&q=";
			
			if (sender is MNMenuItem) 
			{
				TreeView tv = (sender as MNMenuItem).Tv;
				TreeIter iter;
				if (GetCurrentTreeViewIter (tv, out iter))
				{
					MNSynchronisedFSObject syncObj = (MNSynchronisedFSObject)tv.Model.GetValue (iter, 0);
					if (syncObj != null) {
						string searchString = syncObj.Name.Replace(".", "+");
						
						MNTextEntryDialog textDialog = new MNTextEntryDialog ("Search IMDB", "Edit the text before searching IMDB", searchString);
						
						if (textDialog.Run () == (int)Gtk.ResponseType.Ok) {
							searchString = textDialog.Text.Replace (" ", "+");
							System.Diagnostics.Process.Start (baseURL + searchString);
						}
						
						textDialog.Destroy ();					
					}
				}
			}
		}
		
		
		[GLib.ConnectBeforeAttribute]
		protected virtual void OnTreeviewFoldersButtonPressEvent (object o, Gtk.ButtonPressEventArgs args) {
			if (args.Event.Button == 3) /* right click */
			{
				Menu m = new Menu();
				
				m.Add((MenuItem)SyncDirectoryThereAction.CreateMenuItem());
				m.Add((MenuItem)SyncDirectoryHereAction.CreateMenuItem());
				m.Add(new SeparatorMenuItem());
				if(_location.CurrentLocation == MNFSObjectLocation.Destination) {
					m.Add((MenuItem)PrivateDirectoryAction.CreateMenuItem());
				}
				
				m.Add((MenuItem)IgnoreDirectoryAction.CreateMenuItem());
				m.Add(new SeparatorMenuItem());
				MNMenuItem searchIMDB = new MNMenuItem (@"Search IMDB", treeviewFolders);
				searchIMDB.Activated += new EventHandler (OnSearchIMDB);
				m.Add(searchIMDB);
				
				m.ShowAll();
				m.Popup();
			}			
		}	
		
		[GLib.ConnectBeforeAttribute]		
		protected virtual void OnTreeviewFilesButtonPressEvent (object o, Gtk.ButtonPressEventArgs args) {
			if (args.Event.Button == 3) /* right click */
			{
				Menu m = new Menu();
				
				m.Add((MenuItem)SyncFileThereAction.CreateMenuItem());
				m.Add((MenuItem)SyncFileHereAction.CreateMenuItem());
				m.Add(new SeparatorMenuItem());
				if(_location.CurrentLocation == MNFSObjectLocation.Destination) {
					m.Add((MenuItem)PrivateFileAction.CreateMenuItem());
				}
				m.Add((MenuItem)IgnoreFileAction.CreateMenuItem());
				m.Add (new SeparatorMenuItem ());
				MNMenuItem searchIMDB = new MNMenuItem (@"Search IMDB", treeviewFiles);
				searchIMDB.Activated += new EventHandler (OnSearchIMDB);				
				m.Add (searchIMDB);
				
				m.ShowAll();
				m.Popup();
			}			
		}
		
		protected virtual void OnTreeviewFoldersRowActivated (object o, Gtk.RowActivatedArgs args) {
			TreeIter iter;
			if (GetCurrentTreeViewIter(treeviewFolders, out iter)) {
				treeviewFolders.ExpandRow(treeviewFolders.Model.GetPath(iter), false);
			}			
		}		
		
		//******* tree event helpers
		
		private bool GetCurrentTreeViewIter(Gtk.TreeView tv, out TreeIter iter) {
			iter = new TreeIter();
				
			TreePath currentPath;
			TreeViewColumn currentColumn;
			tv.GetCursor(out currentPath, out currentColumn);
				
			if(currentPath != null) {
				if(tv.Model.GetIter(out iter,currentPath)) {
					return true;
				} else {
					return false; //not model data found
				}
			} else {
				return false; //nothing selected
			}
		}

		private void ProcessSelectedFSObjects(TreeView tv, HandleMultiSelectedFSObjects handler, bool UpdateValue) {
			ProcessSelectedFSObjects(tv, handler, UpdateValue, MNFSObjectLocation.NumberOfLocations);
		}
		
		private void ProcessSelectedFSObjects(TreeView tv, HandleMultiSelectedFSObjects handler, bool UpdateValue, MNFSObjectLocation location) {
			HandleMultiSelectedFSObjectsInfo info = new HandleMultiSelectedFSObjectsInfo();
			info.Location = location;
			info.UpdateValue = UpdateValue;
			
			TreePath[] paths = tv.Selection.GetSelectedRows();					
			foreach (TreePath item in paths) {
				TreeIter iter;
				if(tv.Model.GetIter(out iter, item)) {
					info.SyncObj = (MNSynchronisedFSObject)tv.Model.GetValue(iter, 0);				
					handler(info);
				}
			}
			tv.QueueDraw();
		}
		
		private void SetPrivate(TreeView tv, bool updateValue) {
			ProcessSelectedFSObjects(tv, SetPrivateHandler, updateValue);
			_controller.HasChanged = true;
		}
		
		private void SetSyncToForLocation(TreeView tv, MNFSObjectLocation location, bool UpdateValue) {
			
			ProcessSelectedFSObjects(tv, SetSyncToForLocation, UpdateValue, location);
			_controller.HasChanged = true;
		}
		
		private void SetIgnore(TreeView tv, bool UpdateValue) {
			ProcessSelectedFSObjects(tv, SetIgnoreHandler, UpdateValue);
			_controller.HasChanged = true;
		}
		
		private void SetIgnoreHandler(HandleMultiSelectedFSObjectsInfo info) {
			info.SyncObj.Ignore = info.UpdateValue;
		}

		private void SetPrivateHandler(HandleMultiSelectedFSObjectsInfo info) {
			if(info.SyncObj is MNSynchronisedDirectory) {
				(info.SyncObj as MNSynchronisedDirectory).Private = info.UpdateValue;
			} else {
				info.SyncObj.Private = info.UpdateValue;
			}
		}
		
		private void SetSyncToForLocation(HandleMultiSelectedFSObjectsInfo info) {
			info.SyncObj.ToggleSynchroniseToForLocation(info.Location);
		}		
		
		#endregion
			
		#region events (scanning, copying and save/load)
		
		protected virtual void OnScanning(MNEventArgs e)
		{
			//update the progressbar on the main thread.
			Gtk.Application.Invoke (delegate {
				if(e.Complete) {				
					ScanningComplete(e.Message, e.LongMessage, e.Number);				
				} else {
					//still scanning, message is the current folder
					progressbarProcessing.Text = e.Message;
				}
			});
		}
		
		private void ScanningComplete (string message, string longMessage, int number)
		{
			
			stopAction.Sensitive = false;
			stopAction.Tooltip = @"Stop";
			_controller.CancelAction = false;
			progressbarProcessing.Fraction = 1;
			GLib.Timeout.Add (10000, new GLib.TimeoutHandler (ClearLabelStatus));
			scanAction.Sensitive = true;
			QueueAction.Sensitive = true;
			preferencesAction.Sensitive = true;
			treeviewFolders.Sensitive = true;
			treeviewFiles.Sensitive = true;
			
			if(message != @"Error") {				
				ObjectToControls();
				TimeSpan span = DateTime.Now.Subtract(_activityStartTime);		
				string finishedMessage = string.Format(@" Scan {0} in {1} seconds.", message, Math.Round(span.TotalSeconds, 0).ToString());
				if(number > 0) {
					finishedMessage = string.Format(@"{0} Found {1} new objects", finishedMessage, number);
				}
				progressbarProcessing.Text = finishedMessage;
			} else {
				// TODO: rollback any scanning changes.
				progressbarProcessing.Text = @"Error";
				ShowErrorDialog(string.Format(@"Error: {0}", longMessage));
			}
		}
		
		protected virtual void OnCopying(MNEventArgs e)
		{
			//update the progressbar on the main thread.
			Gtk.Application.Invoke (delegate {
				if(e.Complete) {				
					CopyingComplete(e.Message, e.LongMessage);				
				} else {
					//get the time taken so far
					string eta = _controller.Queue.GetETA(_activityStartTime, e.Number);
					labelQueueInfo.Text = string.Format(@"Files: {0}, Total Size: {1}, Completed {2}, ETA {3}", 
					                                    _controller.Queue.Count, _controller.Queue.QueueSize(),
					                                    e.Number, eta);
					progressbarProcessing.Text = string.Format(@"{0}", e.Message);
					progressbarProcessing.Fraction = ((double)e.Number / _controller.Queue.Count);
					treeviewQueue.QueueDraw();
				}
			});
		}
		
		private void CopyingComplete(string message, string longMessage) {	
			stopAction.Sensitive = false;
			stopAction.Tooltip = @"Stop";
			_controller.CancelAction = false;
			progressbarProcessing.Fraction = 1;
			GLib.Timeout.Add (10000, new GLib.TimeoutHandler (ClearLabelStatus));
			scanAction.Sensitive = true;	
			QueueAction.Sensitive = true;
			FilesAction.Sensitive = true;
			copyAction.Sensitive = true;
			preferencesAction.Sensitive = true;
			
			if(message != @"Error") {
				//requeue any files not copied
				_controller.Enqueue();
				treeviewQueue.QueueDraw();
				TimeSpan span = DateTime.Now.Subtract(_activityStartTime);		
				progressbarProcessing.Text = @"Copy " + message + " in " + Math.Round(span.TotalSeconds, 0).ToString() + @" seconds";
			} else {
				// TODO: rollback any queuing changes.
				progressbarProcessing.Text = @"Error";
				ShowErrorDialog(string.Format(@"Error: {0}", longMessage));
			}
		}
		
		protected virtual void OnSaveLoad(MNEventArgs e) {
			//update the progressbar on the main thread.
			Gtk.Application.Invoke (delegate {
				if(e.Complete) {
					if(e.Message == @"Loading") {
						LoadComplete();
					} else {
						SaveComplete();
					}
					//SaveLoadComplete(e.Message, e.LongMessage, e.Number);				
				} else {
					//still save/loading, message is save or load
					if(_busyWindow != null) {
						_busyWindow.Message = e.Message;
						_busyWindow.Pulse();
					}
				}
			});			
		}
		
		private void SaveComplete() {
			_busyWindow.Visible = false;
		}
		
		#endregion
		
		#region display stuff
		
		private void ShowErrorDialog(string message) {
				MessageDialog md = 
					new MessageDialog(this,
			                     DialogFlags.Modal,
			                     MessageType.Error,
			                     ButtonsType.Ok,
			                     message);
				md.Run();
				md.Destroy();			
		}
		
		
		private bool ClearLabelStatus() {
			progressbarProcessing.Text = @"";
			progressbarProcessing.Fraction = 0;
			//progressbarProcessing.Visible = false;
			return false; //stop the timer.
		}
		
		private bool StatusPulse() {
			if(stopAction.Sensitive)
				progressbarProcessing.Pulse();
			return stopAction.Sensitive;
		}
		
		private void ObjectToControls() {
			
			if (_location.CurrentLocation == MNFSObjectLocation.Source) {
				labelStatusBarLocation.Text = @"Monster";
				//if at source hide privacy controls
				PrivateFileAction.Visible = false;
				PrivateDirectoryAction.Visible = false;				
			} else {
				labelStatusBarLocation.Text = @"Home";
				PrivateFileAction.Visible = true;
				PrivateDirectoryAction.Visible = true;						
			}			
			
			ClearLabelStatus();
						
			DirectoriesToTreeView();
			SyncFilesToTreeView(_controller.RootDirectory);
			QueueToControls();
			//GetFreeDiskSpace();
		}
		
		private void QueueToControls() {
			QueueAction.Label = string.Format(@"_Queue ({0})", _controller.Queue.Count);
			toolbarMain.QueueDraw();
			ListStore queueStore = new ListStore(typeof(MNQueuedFile));
			
			if(_controller.Queue != null) {
				foreach(MNQueuedFile qFile in _controller.Queue) {
					queueStore.AppendValues(qFile);
				}
			}
			
			treeviewQueue.Model = queueStore;
			
			treeviewQueue.Reorderable = true;
			treeviewQueue.Model.RowsReordered += new RowsReorderedHandler(OnQueueReordered);
			
			labelQueueInfo.Text = string.Format(@"Files: {0}, Total Size: {1}", _controller.Queue.Count, _controller.Queue.QueueSize());

		}		
		
		private void OnQueueReordered (object obj, RowsReorderedArgs reorder)
		{
			Console.WriteLine ("User reordered queue rows.");
		}
				
		
		private void SyncFilesToTreeView (MNSynchronisedDirectory currentDir)
		{
			TreeStore fileStore = new TreeStore (typeof(MNSynchronisedFile));
			if (currentDir != null) {
				foreach (KeyValuePair<string, MNSynchronisedFile> kvp in currentDir.Files) {
					if (!_lastScanOnly | _controller.LastScan == (kvp.Value.GetDateFoundForLocation (_location.CurrentLocation))) {
						if (!_controller.ExtensionsToIgnore.Contains (kvp.Value.Extension) && kvp.Value.Show (_location.CurrentLocation)) {
							if(!((_location.HideIgnored & kvp.Value.Ignore) | (_location.HidePrivate & kvp.Value.Private))) {							
								fileStore.AppendValues(kvp.Value);
							}							
						}
					}
				}
			}
			
			_filterFiles = new TreeModelFilter(fileStore, null);
			_filterFiles.VisibleFunc = new TreeModelFilterVisibleFunc(FilterFilesTree);
			
			_sortFiles = new TreeModelSort(_filterFiles);
			_sortFiles.SetSortColumnId(0, SortType.Ascending);
			_sortFiles.SetSortFunc(0, SortSynchronisedFSObjectTree);
			treeviewFiles.Model = _sortFiles;
		}
		
		private void DirectoriesToTreeView() {	
			TreeStore directoryStore = new TreeStore(typeof(MNSynchronisedDirectory));
			TreeIter iter = directoryStore.AppendValues(_controller.RootDirectory);
			SyncDirsInTreeStore(directoryStore, _controller.RootDirectory, iter);
			
			_filterDirectories = new TreeModelFilter(directoryStore, null);
			_filterDirectories.VisibleFunc = new Gtk.TreeModelFilterVisibleFunc(FilterDirectoriesTree);
			
			_sortDirectories = new TreeModelSort(_filterDirectories);
			_sortDirectories.SetSortColumnId(0, SortType.Ascending);
			_sortDirectories.SetSortFunc(0, SortSynchronisedFSObjectTree);
			treeviewFolders.Model = _sortDirectories;
			
			//expand the root directory
			if(_filterDirectories.GetIterFirst(out iter)) {
				treeviewFolders.ExpandRow(_filterDirectories.GetPath(iter), false);
			}
		}
		
		private void SyncDirsInTreeStore(TreeStore store, MNSynchronisedDirectory syncDir, TreeIter iter)
		{
			if(syncDir != null) {
				foreach(KeyValuePair<string, MNSynchronisedDirectory> kvp in syncDir.Directories)
				{
					Console.WriteLine(string.Format(@"",kvp.Key, kvp.Value.GetDateFoundForLocation(_location.CurrentLocation)));
					if(!_lastScanOnly | _controller.LastScan == (kvp.Value.GetDateFoundForLocation(_location.CurrentLocation))) {
					   	if(!((_location.HideIgnored & kvp.Value.Ignore) | (_location.HidePrivate & kvp.Value.Private))) {
							
							if(kvp.Value.Show(_location.CurrentLocation)) {
								TreeIter child = store.AppendValues(iter, kvp.Value);
								SyncDirsInTreeStore(store, kvp.Value, child);
							}
						}
					}
				}
			} 
		}	
		

		
		#endregion
		
		#region disk usage
		
		private void GetFreeDiskSpace() {
			//System.IO.DirectoryInfo aDir = new System.IO.DirectoryInfo(_location.Path);
			//System.IO.DriveInfo aDrive = new System.IO.DriveInfo(_location.Path);
			//Mono.Unix.UnixDriveInfo driveInfo = Mono.Unix.UnixDriveInfo.GetForSpecialFile(_location.Path);
			System.IO.DriveInfo[] drives = System.IO.DriveInfo.GetDrives(); //Mono.Unix.UnixDriveInfo.GetDrives();
			//System.IO.DriveInfo.GetDrives();
			
			foreach (System.IO.DriveInfo aDrive in drives) {
				//labelFreeSpace.Text = aDrive.AvailableFreeSpace.ToString();
				Console.WriteLine(string.Format(@"{0}: {1}", 
				                                aDrive.VolumeLabel,
				                                MNGlobal.GetSizeFromUlongToString((ulong)aDrive.TotalFreeSpace)));
			}
			//labelFreeSpace.Text = MNGlobal.GetSizeFromUlongToString((ulong)driveInfo.AvailableFreeSpace);
				
				
		}
		
		#endregion
		
		#region tree view render cells (helpers)
		private void SetCellTextStyle(Gtk.CellRendererText cell, MNSynchronisedFSObject syncObj) {		
						
			cell.Strikethrough = false;
			
			if(syncObj.GetExistsAtForLocation(_location.CurrentLocation)) {
				cell.Foreground = "black";
				
				if(syncObj.GetDateFoundForLocation(_location.CurrentLocation) == _controller.LastScan && syncObj.Name != System.IO.Path.DirectorySeparatorChar.ToString()) {
					cell.Underline = Pango.Underline.Low;
				} else {
					cell.Underline = Pango.Underline.None;
				}				
			} else {
				cell.Foreground = "blue";
				cell.Underline = Pango.Underline.None;
			}
			
			if(syncObj.Ignore) {
				cell.Strikethrough = true;
				cell.Underline = Pango.Underline.None;
			} 
			
		}
		
		#endregion
		
		#region tree view render cells (directory)
		
		private void RenderDirectoryFolderNameCell(Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
		{
			string displayText = string.Empty;
			
			MNSynchronisedDirectory syncDir = (MNSynchronisedDirectory)model.GetValue (iter, 0);
			if(syncDir != null) {
				displayText = syncDir.Name;
				SetCellTextStyle((cell as CellRendererText), syncDir);
			} 			
			(cell as Gtk.CellRendererText).Text = displayText;
		}
		
		private void RenderDirectoryFolderIconCell(Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter) {
			MNSynchronisedDirectory syncDir = (MNSynchronisedDirectory)model.GetValue (iter, 0);
			if(syncDir != null) {
				if(syncDir.Private) {
					(cell as Gtk.CellRendererPixbuf).StockId = "PrivateFolder";
				} else if(syncDir.ChildrenInSync) {
					(cell as Gtk.CellRendererPixbuf).StockId = "insync";
				} else if(syncDir.GetSynchroniseToForLocation(_location.CurrentLocation)) {
					(cell as Gtk.CellRendererPixbuf).StockId = "copydown";
				} else if(syncDir.GetSynchroniseToForLocation(_location.OppositeLocation)) {
					(cell as Gtk.CellRendererPixbuf).StockId = "copyup";
				} else {
					(cell as Gtk.CellRendererPixbuf).StockId = "DirectoryClosed";
				}
				
					
				//TODO: UP AND DOWN:GetSynchroniseToForLocation(MNFSObjectLocation.Destination) && syncDir.GetSynchroniseToForLocation(MNFSObjectLocation.Source))
			}
		}			
			
		private void RenderDirectorySizeCell(Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
		{
			MNSynchronisedDirectory syncDir = (MNSynchronisedDirectory) model.GetValue (iter, 0);
			CellRendererText textCell = (cell as Gtk.CellRendererText);
			textCell.Alignment = Pango.Alignment.Right;
			
			if(syncDir != null) {
				SetCellTextStyle((cell as CellRendererText), syncDir);
				if(syncDir.GetExistsAtForLocation(_location.CurrentLocation)) {
					(cell as Gtk.CellRendererText).Text = syncDir.GetSizeAsStringForLocation(_location.CurrentLocation);
				} else {
					(cell as Gtk.CellRendererText).Text = syncDir.GetSizeAsStringForLocation(_location.OppositeLocation);					
				}				
			} else {
				textCell.Text = string.Empty;
			}
		}
		
		#endregion
		#region tree view render cells (file)

		private void RenderFilenameCell(Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
		{
			string displayText = string.Empty;
			
			MNSynchronisedFile syncFile = (MNSynchronisedFile)model.GetValue (iter, 0);
			if(syncFile != null) {
				SetCellTextStyle((cell as CellRendererText), syncFile);
				displayText = syncFile.Name;							
			}			
			(cell as Gtk.CellRendererText).Text = displayText;
		}
		
		private void RenderFilenameIconCell (Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
		{
			MNSynchronisedFile syncFile = (MNSynchronisedFile)model.GetValue (iter, 0);
			if (syncFile != null) {
				if (syncFile.Private) {
					(cell as Gtk.CellRendererPixbuf).StockId = "PrivateFile";
				} else if (syncFile.GetIsSynchronised ()) {
					(cell as Gtk.CellRendererPixbuf).StockId = "insync";
				} else if (syncFile.GetExistsAtForLocation(MNFSObjectLocation.Portable)) {
					(cell as Gtk.CellRendererPixbuf).StockId = "intransit";
				} else if (syncFile.GetSynchroniseToForLocation (_location.CurrentLocation)) {
					(cell as Gtk.CellRendererPixbuf).StockId = "copydown";
				} else if(syncFile.GetSynchroniseToForLocation(_location.OppositeLocation)) {
					(cell as Gtk.CellRendererPixbuf).StockId = "copyup";
				} else {
					(cell as Gtk.CellRendererPixbuf).StockId = "File";
				}
			}
		}
		
		private void RenderFileSizeCell(Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
		{
			MNSynchronisedFile syncFile = (MNSynchronisedFile) model.GetValue (iter, 0);
			(cell as Gtk.CellRendererText).Alignment = Pango.Alignment.Right;
			if(syncFile != null) {
				SetCellTextStyle((cell as CellRendererText), syncFile);
				if(syncFile.GetExistsAtForLocation(_location.CurrentLocation)) {
					(cell as Gtk.CellRendererText).Text = syncFile.GetSizeAsStringForLocation(_location.CurrentLocation);
				} else {
					(cell as Gtk.CellRendererText).Text = syncFile.GetSizeAsStringForLocation(_location.OppositeLocation);
				}
			} else {
				(cell as Gtk.CellRendererText).Text = string.Empty;
			}
		}
		
		private void RenderFileDateModifiedCell(Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
		{
			MNSynchronisedFile syncFile = (MNSynchronisedFile) model.GetValue (iter, 0);
			if(syncFile != null) {
				SetCellTextStyle((cell as CellRendererText), syncFile);				
				if(syncFile.GetExistsAtForLocation(_location.CurrentLocation)) {
					(cell as Gtk.CellRendererText).Text = syncFile.GetDateModifiedAsStringForLocation(_location.CurrentLocation);
				} else {
					(cell as Gtk.CellRendererText).Text = syncFile.GetDateModifiedAsStringForLocation(_location.OppositeLocation);			
				}				
			} else {
				(cell as Gtk.CellRendererText).Text = string.Empty;
			}	
		}
		
		#endregion
		#region tree view render cells (queue)
		
		private void RenderQueueNameCell(Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
		{
			MNQueuedFile qFile = (MNQueuedFile) model.GetValue (iter, 0);
			if(qFile != null) {
				(cell as Gtk.CellRendererText).Background = qFile.GetStatusColour;
				(cell as Gtk.CellRendererText).Text = qFile.Name;
			} else {
				(cell as Gtk.CellRendererText).Text = string.Empty;
				(cell as Gtk.CellRendererText).Background = MNQueuedFile.GetColourForStatus(MNQueuedFileStatus.Failed);
			}
		}
		
		private void RenderQueuePathCell(Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
		{
			MNQueuedFile qFile = (MNQueuedFile) model.GetValue (iter, 0);
			if(qFile != null) {
				(cell as Gtk.CellRendererText).Background = qFile.GetStatusColour;
				(cell as Gtk.CellRendererText).Text = qFile.RootlessPath;
			} else {
				(cell as Gtk.CellRendererText).Text = string.Empty;
				(cell as Gtk.CellRendererText).Background = MNQueuedFile.GetColourForStatus(MNQueuedFileStatus.Failed);
			}
		}

		private void RenderQueueFromCell(Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
		{
			MNQueuedFile qFile = (MNQueuedFile) model.GetValue (iter, 0);
			if(qFile != null) {
				(cell as Gtk.CellRendererText).Background = qFile.GetStatusColour;
				(cell as Gtk.CellRendererText).Text = MNGlobal.GetNameOfLocation(qFile.LocationFrom);
			} else {
				(cell as Gtk.CellRendererText).Text = string.Empty;
				(cell as Gtk.CellRendererText).Background = MNQueuedFile.GetColourForStatus(MNQueuedFileStatus.Failed);
			}
		}

		private void RenderQueueToCell(Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
		{
			MNQueuedFile qFile = (MNQueuedFile) model.GetValue (iter, 0);
			if(qFile != null) {
				(cell as Gtk.CellRendererText).Background = qFile.GetStatusColour;
				(cell as Gtk.CellRendererText).Text = MNGlobal.GetNameOfLocation(qFile.LocationTo);
			} else {
				(cell as Gtk.CellRendererText).Text = string.Empty;
				(cell as Gtk.CellRendererText).Background = MNQueuedFile.GetColourForStatus(MNQueuedFileStatus.Failed);
			}
		}
		
		private void RenderQueueSizeCell(Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
		{
			MNQueuedFile qFile = (MNQueuedFile) model.GetValue (iter, 0);
			(cell as Gtk.CellRendererText).Alignment = Pango.Alignment.Right;
			if(qFile != null) {
				(cell as Gtk.CellRendererText).Background = qFile.GetStatusColour;
				(cell as Gtk.CellRendererText).Text = MNGlobal.GetSizeFromUlongToString(qFile.SizeInBytes);
			} else {
				(cell as Gtk.CellRendererText).Text = string.Empty;
				(cell as Gtk.CellRendererText).Background = MNQueuedFile.GetColourForStatus(MNQueuedFileStatus.Failed);
			}
		}
		
		#endregion					

	}
}

